package net.borderwars.vm;


import org.apache.bcel.classfile.*;
import org.apache.bcel.generic.*;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

/**
 * @author Eric
 *         Date: Jun 22, 2004
 *         Time: 9:46:43 PM
 */
public class VirtualMachine {






    private static long gid = 0;
    private int tick = 0;
    public boolean standardOut = false;
    public boolean standardErr = false;
    private boolean debugStack = true;


    private VMState state = null;
    public VMState getState() {
        return (state);
    }

    HashMap<String, MethodStruct> methodByName = new HashMap<String, MethodStruct>();
    
    private long id = gid++;


    private ConstantPoolGen cpg = null;
    private Object robotInstance = null;
    private String name = null;


    private MethodStruct mainMethod = null;
    HashMap<Method, MethodStruct> methodByBcelMethod = new HashMap<Method, MethodStruct>();


    private HashMap<String, Class<? extends Number>> classByType = null;
    private boolean allowBooleans;
    private LinkedList<String> debugStatements = new LinkedList<String>();
    private JavaClass jc;


    public void dumpDebug() {
        for (String s : debugStatements) {
            System.out.println(s);
        }
    }


    public Stack<Object> getStack() {
        return state.stack;
    }

    public Object getRobotInstance() {
        return robotInstance;
    }

    public void restart() {
        state.stack.clear();
        push(42); // this is a pretend marker for the 1 fake argument of main
        push(42); // this is a pretend marker for the 1 fake argument of main
        pointAtNewMethod(mainMethod, null);
    }

    public VirtualMachine(String fileName, byte data[], boolean allowBooleans) throws IOException {
        this.allowBooleans = allowBooleans;
        state = new VMState();
        String methodName = "start";

        ClassParser cp = new ClassParser(new ByteArrayInputStream(data), fileName);
        jc = cp.parse();
        name = jc.getClassName();
        Method amethods[];
        initializeFields(jc);

        robotInstance = new DumbRobot();

        amethods = jc.getMethods();
        ConstantPool constantPool = jc.getConstantPool();
        cpg = new ConstantPoolGen(constantPool);
        MethodStruct ms = null;
        for (Method method : amethods) {
            ms = new MethodStruct(this, method.getName());
            ms.bcelMethodCode = method.getCode();
            ms.bcelMethodCode.getLineNumberTable();
            ms.setMethodBytes(ms.bcelMethodCode.getCode(), name, method.getName());
            if (method.toString().indexOf(methodName) != -1) {
                mainMethod = ms;
            }
            ms.setMethod(method);
        }
        if (mainMethod == null) {
            throw new UnsupportedOperationException("There must be a method by the name of start to call");
        }
        //Class2HTML c = new Class2HTML(jc, "");

        push(42);
        //  push(robotInstance); // this is a pretend marker for the 1 fake argument of main
        pointAtNewMethod(mainMethod, null);
        // testLoop();

    }

    private void initializeFields(JavaClass jc) {
        org.apache.bcel.classfile.Field[] fieldArray = jc.getFields();
        for (org.apache.bcel.classfile.Field f : fieldArray) {
            state.setField(f.getName(), 0);
        }
    }

    public Object pop() {
        if (debugStack) {

            if (state.stack.size() <= 1 && state.stack.peek() instanceof ReturnMarker) {
                throw new EmptyStackException();
            }

            DebugStackElement dse = (DebugStackElement) state.stack.pop();
            if (dse.value instanceof ReturnMarker) {

                outln(id + " " + getName() + " popped " + dse.value);
                if (state.stack.size() == 0)       {

                    throw new EmptyStackException();
                }
            }
            return (dse.value);
        } else {
            if (state.stack.size() == 1 && state.stack.peek() instanceof ReturnMarker) {
                throw new EmptyStackException();
            }
            Object o = state.stack.pop();
            if (o instanceof ReturnMarker) {
                outln(id + " " + getName() + " popped " + o);
                if (state.stack.size() == 0)
                    throw new EmptyStackException();
            }
            return (o);
        }
    }

    public void push(Object o, Object by) {

        if (state.stack.size() > 64) {
            throw new StackOverflowException(state.stack, debugStack);
        }
        if (!allowBooleans) {
            if (o instanceof Boolean) {
                Boolean aBoolean = (Boolean) o;
                if (aBoolean)
                    o = 1;
                else
                    o = 0;
            }
        }

        if (debugStack) {
            o = new DebugStackElement(o, state.getPC(), state.currentMethod.name);
        }

        state.stack.push(o);
    }

    private void push(Object o) {
        push(o, "Unknown");
    }

    void errln(String s) {
        if (standardErr) {
            System.err.println(s);
        }
    }

    public int getCurrentTick() {
        return (tick);
    }

    public class DebugStackElement {
        private String method;
        private int idx;
        private Object value;

        public DebugStackElement(Object value, int idx, String method) {
            this.value = value;
            this.idx = idx;
            this.method = method;
        }

        public String getMethod() {
            return method;
        }

        public int getIdx() {
            return idx;
        }

        public Object getValue() {
            return value;
        }

        public String toString() {
            return (value.toString());
        }

    }

// -------------------------- OTHER METHODS --------------------------

// -------------------------- Public METHODS --------------------------

    public String getName() {
        return name;
    }


    public String makeStringException(Exception e) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        return (pw.toString());
    }

    // TODO: Check for Interrupts... hit, dead, fire.. things like that
    public Interupt intCheck() {
        return (Interupt.NONE);
    }

    public boolean nextInstruction() throws IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchFieldException, RestartException {
        boolean rtn = false;
        rtn = executeNextInstruction();
        return (rtn);
    }

    Iterator<Integer> currentInstructionIterator = null;

    private boolean executeNextInstruction() throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException, RestartException {
        if (state.getStatus().equals(VMState.STATUS.EXITED)) {
            return (true);
        }

        if (!currentInstructionIterator.hasNext()) { // very odd!
            outln("Very odd we are out of instructions?");
            outln("Current index is " + state.getPC());
            outln("here is our program of size " + state.currentMethod.instructions.keySet().size());
            for (Integer idx : state.currentMethod.instructions.keySet()) {
                outln(idx + " " + state.currentMethod.instructions.get(idx).toString(jc.getConstantPool()));
            }

        }

        try {
            state.setPC( currentInstructionIterator.next());
        }
        catch (NoSuchElementException e) {
            throw new RestartException(e);
        }
        Instruction i = state.currentMethod.instructions.get(state.getPC());

//        currentIndex = bs.getIndex();
//        try {
//            i = Instruction.readInstruction(bs);
//        } catch (IOException encoder) {
//            return (true);
//        }

        //outln(id + ":" + currentMethod.name + "[" + currentIndex + "] Fetched: " + i + " ");
        //System.err.println(id + ":" + currentMethod.name + "[" + currentIndex + "] fetchered " + i);
        if (i == null) {
            outln("Got a null instruction? " + state.currentMethod.instructions + " currentIndex " + state.getPC());

        }
        System.out.println("Executing " + i);
        switch (i.getOpcode()) {
            case 0:    // NOP
                break;
            case 9:
            case 10:
                execute((LCONST) i);
                break;
            case 178:
                execute((GETSTATIC) i);
                break;
            case 172:
                execute((IRETURN) i);
                break;
            case 112:
                execute((IREM) i);
                break;
            case 187:
                execute((NEW) i);
                break;
            case 153:
                execute((IFEQ) i);
                break;
            case 76:
                execute((ASTORE) i);
                break;
            case 42:
            case 43:
                execute((ALOAD) i);
                break;
            case 16:
                execute((BIPUSH) i);
                break;
            case 182:
                execute((INVOKEVIRTUAL) i);
                break;
            case 184:
                execute((INVOKESTATIC) i);
                break;
            case 18:
                execute((LDC) i);
                break;
            case 177:
                execute((RETURN) i);
                break;
            case 89:
                execute((DUP) i);
                break;
            case 180:
                execute((GETFIELD) i);
                break;
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
            case 8:
                execute((ICONST) i);
                break;

            case 100:
                execute((ISUB) i);
                break;
            case 96:
                execute((IADD) i);
                break;
            case 157:
                execute((IFGT) i);
                break;
            case 181:
                execute((PUTFIELD) i);
                break;
            case 17:
                execute((SIPUSH) i);
                break;
            case 59:
            case 54:
            case 60:
            case 61:
            case 62:
                execute((ISTORE) i);
                break;
            case 21:
            case 26:
            case 29:
            case 28:
            case 27:
                execute((ILOAD) i);
                break;
            case 90:
                execute((DUP_X1) i);
                break;
            case 104:
                execute((IMUL) i);
                break;
            case 108:
                execute((IDIV) i);
                break;
            case 158:
                execute((IFLE) i);
                break;
            case 154:
                execute((IFNE) i);
                break;
            case 156:
                execute((IFGE) i);
                break;
            case 160:
                execute((IF_ICMPNE) i);
                break;
            case 161:
                execute((IF_ICMPLT) i);
                break;
            case 162:
                execute((IF_ICMPGE) i);
                break;
            case 163:
                execute((IF_ICMPGT) i);
                break;
            case 164:
                execute((IF_ICMPLE) i);
                break;
            case 159:
                execute((IF_ICMPEQ) i);
                break;
            case 132:
                execute((IINC) i);
                break;
            case 167:
                execute((GOTO) i);
                break;
            case 1:
                execute((ACONST_NULL) i);
                break;
            case 183:
                execute((INVOKESPECIAL) i);
                break;
            case 87:
                execute((POP) i);
                break;
            case 120:
                execute((ISHL) i);
                break;
            case 122:
                execute((ISHR) i);
                break;
            default:
                outln("Unknown opcode: " + i.getOpcode() + " " + i.toString());
                throw new UnsupportedOperationException("Unknown opcode: " + i.getOpcode() + " " + i.toString());
        }
        outln("");
        return (false); // return true at the end of our program
    }

    private void execute(ISHL i) {
        Number shiftThisFar = (Number) pop();
        Number shiftThis = (Number) pop();
        int in = shiftThis.intValue() << shiftThisFar.intValue();
        push(in);

    }

    private void execute(ISHR i) {
        Number shiftThisFar = (Number) pop();
        Number shiftThis = (Number) pop();
        int in = shiftThis.intValue() >> shiftThisFar.intValue();
        push(in);

    }

    private void execute(LCONST i) {
        push(i.getValue());

    }

    private void execute(IDIV i) {
        Number one = (Number) pop();
        Number two = (Number) pop();
        if (two.intValue() == 0) {
            two = 1;
        }
        push(one.intValue() / two.intValue());
    }

    private void execute(IMUL i) {
        Number one = (Number) pop();
        Number two = (Number) pop();
        push(one.intValue() * two.intValue());
    }

    private void execute(DUP_X1 i) {
        Object one = pop();
        Object two = pop();
        push(one);
        push(two);
        push(one);
    }

    private void execute(POP p) {
        pop();
    }

    private void execute(GETSTATIC i) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Object o = pop(); // grab a refence to the object whos static field we are gonna get
        String fieldName = i.getFieldName(cpg);
        String className = i.getClassName(cpg);
        Class c = Class.forName(className);
        Field f = c.getField(fieldName);
        Object fieldValue = f.get(null);
        String localName = i.getName(cpg);
        push(fieldValue); // put the value of the field back on the stack
    }

    private void execute(ISUB i) {
        i.getLength();
        Object oa = null;
        try {
            oa = pop();
            Number a = (Number) oa;
            Number b = (Number) pop();
            //   System.err.println(a + "," + b);
            Integer c = a.intValue() - b.intValue();
            push(c);
        }
        catch (ClassCastException e) {
            outln(oa.toString());

            e.printStackTrace();
            throw (e);
        }

    }

    private void out(String s) {
        if (standardOut) {
            System.out.print(s);
        }
    }

    private void execute(IRETURN i) {
        i.getLength();
        Object rtnedValue = pop(); // grab the return value
        Object o = null;
        outln("executing IRETURN");
        LinkedList<Object> thingsWePopped = new LinkedList<Object>();
        while (!(o instanceof ReturnMarker)) {
            try {
                o = pop();
                thingsWePopped.add(o);
            }
            catch (EmptyStackException e) {
                outln("We have an EMPTY STACK? what the heck we supposed to do?");
                outln("Our robot is called " + this.getName());
                outln("HEre is our stack " + state.stack);
                if (allowBooleans) {
                    e.printStackTrace();
                }
                throw e;
            }
        }
        ReturnMarker rm = (ReturnMarker) o;
        outln("psuhing return value " + rtnedValue);
        push(rtnedValue);

        outln("Point at new method with " + rm.method + " " + rm);
        pointAtNewMethod(rm.method, rm);
    }

    /**
     * Pops two ints off the operand stack, divides value2 by value1 (i.encoder. value2 / value1),
     * computes the remainder and pushes the int remainder back onto the state.stack. The remainder is
     * (value2 - ((value1 / value2) * value2)).
     * 3   -  ((3/5)*3))
     * This is used by the % operator in Java.
     *
     * @param i
     */
    private void execute(IREM i) {
        i.getLength();
        Number value1 = (Number) pop();
        Number value2 = (Number) pop();
        int l2 = value1.intValue();
        int l1 = value2.intValue();
        if (l2 == 0) l2 = 1; // TODO: fix for dumb randon robots
        // int result =  (l2 - (  ( l1 / l2) * l2));  // spent way to long trying to calculate this
        int result = l1 % l2; // when this worked much easier
        result = Math.abs(result);
        push(result);
    }

    private void execute(NEW i) {
        ObjectType ot = i.getLoadClassType(cpg);
        String cn = ot.getClassName();
        UnIntializedObject uio = new UnIntializedObject(cn);
        push(uio);
    }

    private void execute(ASTORE i) {
        LocalVariableTable lvt = state.currentMethod.bcelMethod.getLocalVariableTable();
        LocalVariable lv = lvt.getLocalVariable(i.getIndex());
        Object o = pop();
        state.currentMethod.localVariables.set(i.getIndex(), (Number) o);
    }

    private void execute(ALOAD i) {
        Object o = state.currentMethod.localVariables.get(i.getIndex());
        push(o, i);
    }

    private void execute(BIPUSH i) {
        Type t = i.getType(cpg);
        Number n = i.getValue();
        push(t, n);
    }

    private void push(Type t, Number n) {
        byte bt = t.getType();
        Object o = null;
        switch (bt) {
            case 8:
                o = n.byteValue();
                break;
            case 10:
                o = n.intValue();
                break;
            case 9:
                o = n.shortValue();
                break;

            default:
                errln("unknown type: " + bt + " " + t.toString());
                throw new UnsupportedOperationException("Uknown type");
        }

        push(o, "PushByType");
    }

    private void execute(INVOKEVIRTUAL i) throws ClassNotFoundException, RestartException {
        genericInvoke(i);
    }

    private void execute(INVOKESTATIC i) throws ClassNotFoundException, RestartException {
        genericInvoke(i);
    }

    private void execute(LDC i) {
        Type t = i.getType(cpg);
        //System.out.println("gonna push " + i.getValue(cpg));
        switch (t.getType()) {
            case 10:
                Integer in = (Integer) i.getValue(cpg);
                push(in);
                break;
            case 14:
                String s = (String) i.getValue(cpg);
                push(s);
                break;
            default:
                System.err.println("We don't support loading these constatns yet");
                System.exit(1);
                break;
        }
    }

    private void execute(RETURN i) {
        i.getLength();
        Object o = pop();
        while (!(o instanceof ReturnMarker)) {
            try {
                o = pop();
            }
            catch (EmptyStackException e) {
                System.err.println("We have an EMPTY STACK? what the heck we supposed to do?");
                System.err.println("Our robot is called " + this.getName());
                System.err.println("HEre is our stack " + state.stack);
                throw e;
            }
        }
        ReturnMarker rm = (ReturnMarker) o;
        //   System.err.println("Point at new method with " + rm.method + " " + rm);
        pointAtNewMethod(rm.method, rm);

    }

    private void execute(DUP dup) {
        dup.getLength();
        Object o = pop();
        push(o);
        push(o);
    }

    private void execute(GETFIELD i) {
        Object o = pop(); // This is areference to the object whos field we are grabbing
        // right now we only support getting fields from the
        // main class... so the code just ignores this
        // System.out.println("We popped this " + o);
        String fn = i.getFieldName(cpg);
        String cn = i.getClassName(cpg);
        Object someFieldValue = state.getField(fn);
        if (someFieldValue == null) {
            new UnsupportedOperationException("we were asked to get a field we don't know about " + cn + ":" + fn);
        }
        push(someFieldValue);
    }

    private void execute(ICONST i) {
        push(i.getType(cpg), i.getValue());
    }

    private void execute(IADD i) {
        i.getLength();
        Number a = (Number) pop();
        Number b = (Number) pop();
        Integer c = a.intValue() + b.intValue();
        push(c);
    }



    private void execute(PUTFIELD i) {
        String fn = i.getFieldName(cpg);
        String cn = i.getClassName(cpg);
        Object value = pop();
        Object instance = pop();
        state.setField(fn, (Number) value);
    }

    private void execute(SIPUSH i) {
        Type t = i.getType(cpg);
        Number n = i.getValue();
        push(t, n);
    }

    private void execute(ISTORE i) {
        // printStack();
        Object o = pop();

//        LocalVariableTable lvt = currentMethod.bcelMethod.getLocalVariableTable();
//        LocalVariable lv = lvt.getLocalVariable(i.getIndex());
//        String fn = lv.getName();
        state.currentMethod.localVariables.set(i.getIndex(), (Number) o);
        //  errln(lvt);
        //  methodLocalVariableValues.put(fn, o);
    }

    private void execute(ILOAD i) {
//        LocalVariableTable lvt = currentMethod.bcelMethod.getLocalVariableTable();
//        LocalVariable lv = lvt.getLocalVariable(i.getIndex());
//        String fn = lv.getName();
        // errln(lvt);
        Number n = state.currentMethod.localVariables.get(i.getIndex());
        if (n == null) {
            //errln("Possible problem here... no such local var! " + fn);
            throw new UnsupportedOperationException("umm... this local variable wasn't present!");
        }
        push(n);
    }

    private void execute(IFLE i) {
        java.lang.Number one = (Number) pop();
        long lone = one.longValue();
        if (lone <= 0) {
            movePc(state.getPC() + i.getIndex());
        }
    }

    private void execute(IFGE i) {
        java.lang.Number one = (Number) pop();
        long lone = one.longValue();
        if (lone >= 0) {
            movePc(state.getPC() + i.getIndex());
        }
    }

    private void execute(IFGT i) {
        java.lang.Number one = (Number) pop();
        long lone = one.longValue();
        if (lone > 0) {
            movePc(state.getPC() + i.getIndex());
        }
    }

    private void execute(IFEQ i) {
        Object o = pop();
        if (o instanceof Boolean) {
            Boolean ab = (Boolean) o;
            if (ab) {
                movePc(state.getPC() + i.getIndex());
            }

        } else {

            Number n = (Number) o;
            if (n.equals(0)) {
                movePc(state.getPC() + i.getIndex());
            }
        }

    }

    private void execute(IF_ICMPEQ i) {
        Number one = (Number) pop();
        Number two = (Number) pop();
        if (two.longValue() == one.longValue()) {
            movePc(state.getPC() + i.getIndex());
        }

    }

    private void execute(IF_ICMPNE i) {
        Number one = (Number) pop();
        Number two = (Number) pop();
        if (two.longValue() != one.longValue()) {
            movePc(state.getPC() + i.getIndex());
        }
    }

    private void execute(IF_ICMPLT i) {
        Number one = (Number) pop();
        Number two = (Number) pop();
        if (two.longValue() < one.longValue()) {
            movePc(state.getPC() + i.getIndex());
        }
    }

    private void execute(IFNE i) {
        Object o = pop();
        if (o instanceof Boolean) {
            Boolean bo = (Boolean) o;
            if (bo) {
                movePc(state.getPC() + i.getIndex());
            }
        } else {
            Number n = (Number) o;
            long l = n.longValue();
            if (l != 0) {
                movePc(state.getPC() + i.getIndex());
            }
        }
        //To change body of created methods use File | Settings | File Templates.
    }

    private void execute(IF_ICMPLE i) {
        //errln("Stack: " + stack);
        java.lang.Number one = (Number) pop();
        java.lang.Number two = (Number) pop();
        // errln("one " + one.getClass() + " two " + two.getClass());
        long lone = one.longValue();
        long ltwo = two.longValue();

        if (ltwo <= lone) {
            movePc(state.getPC() + i.getIndex());
        }
    }

    private void execute(IF_ICMPGT i) {
        //errln("Stack: " + stack);
        java.lang.Number one = (Number) pop();
        java.lang.Number two = (Number) pop();
        long lone = one.longValue();
        long ltwo = two.longValue();

        if (ltwo > lone) {
            movePc(state.getPC() + i.getIndex());
        }

    }

    private void execute(IF_ICMPGE i) {
        //errln("Stack: " + stack);
        java.lang.Number one = (Number) pop();
        java.lang.Number two = (Number) pop();
        // errln("one " + one.getClass() + " two " + two.getClass());
        long lone = one.longValue();
        long ltwo = two.longValue();

        if (ltwo >= lone) {
            movePc(state.getPC() + i.getIndex());
        }
    }

    public int getSourceLineNumber() {
        return (state.currentMethod.bcelMethod.getLineNumberTable().getSourceLine(state.getPC()));
    }

    private void movePc(int i) {
        currentInstructionIterator = state.currentMethod.instructions.tailMap(i).keySet().iterator();
    }

    private void execute(IINC i) {
        int inc = i.getIncrement();
        Number integer = state.currentMethod.localVariables.get(i.getIndex());
        int inty = integer.intValue() + inc;
        state.currentMethod.localVariables.set(i.getIndex(), inty);
    }

    private void execute(GOTO i) {
        int idx = i.getIndex();
        movePc(idx + state.getPC());
    }

    private void execute(ACONST_NULL i) {
        i.getLength();
        push(null);
    }

    private void execute(INVOKESPECIAL i) throws ClassNotFoundException, RestartException {
        genericInvoke(i);
    }

    private void genericInvoke(InvokeInstruction i) throws ClassNotFoundException, RestartException {
        //  errln("GenericInvoke: " + i.getMethodName(cpg));
        String m = i.getMethodName(cpg);
        if (isInvokeAllowed(i)) {

            if (virtualizeThisMethod(i)) {
                MethodStruct ms = (MethodStruct) methodByName.get(m);
                pointAtNewMethod(ms, null);
            } else {  // call on real virtual machine
                outln(" Reflection");
                // Class c = i.getClass(); // dont' do this.. it returns the class for INVOKEVirtual silly!
                // printStack();
                //errln("Stack: " + stack);

                Object target = null;
                Class targetClass = null;
                Type argTypes[] = null;

                argTypes = i.getArgumentTypes(cpg);
                Class argClasses[] = new Class[argTypes.length];
                Object argValues[] = new Object[argTypes.length];
                for (int j = argTypes.length - 1; j >= 0; j--) {
                    argValues[j] = pop();
                    argClasses[j] = getType(argTypes[j]);
                    if (argClasses[j] == null) {
                        argClasses[j] = argValues[j].getClass();
                    }
                }

                if (i instanceof INVOKESTATIC) {
                    INVOKESTATIC is = (INVOKESTATIC) i;
                    ObjectType ot = is.getClassType(cpg);
                    targetClass = Class.forName(ot.getClassName());
                } else { // if its not static.. then after the args.. there is a reference to the instance that we are to callt he method on

                    target = pop();
//                    targetClass = Robot.class;
                    target = robotInstance;
                }

                ArrayList<Class> tmpClasses = new ArrayList<Class>();
                for (Class lvClass : argClasses) {
                    tmpClasses.add(lvClass);
                }
                // errln("Looking for " + m + " on " + targetClass + " with args " + tmpClasses);

                try {

                    java.lang.reflect.Method me = targetClass.getMethod(m, argClasses);

                    if (me == null) {
                        throw new UnsupportedOperationException("Could not find method " + m);
                    }
                    Object rtn = me.invoke(target, argValues);

                    if (!me.getReturnType().toString().equals("void")) {
                        push(rtn);
                    }
                }
                catch (NoSuchMethodException e) {
                    outln("ArgValues " + argValues);
                    e.printStackTrace();
                }
                catch (IllegalAccessException e) {
                    outln("ArgValues " + argValues);
                    e.printStackTrace();
                }
                catch (InvocationTargetException e) {
                    outln("ArgValues " + argValues);
                    e.getCause().printStackTrace();
                }
                catch (IllegalArgumentException e) {
                    outln("ArgValues " + argValues);
                    throw new RestartException(e);
                }

            }
        } else {
            throw new Error(new StringBuffer().append("Attempt to access illegel method ").append(i.toString()).toString());
        }

    }

    private boolean isInvokeAllowed(InvokeInstruction i) {
        String methodName = i.getMethodName(cpg);
        String className = i.getClassName(cpg);
        Type types[] = i.getArgumentTypes(cpg);

        out("Allowing\t" + className + "." + methodName + "(");
        for (int j = 0; j < types.length; j++) {
            Type lvType = types[j];
            if (j == 0) {
                err(lvType.toString());
            } else {
                err("," + lvType.toString());
            }
        }
        out(")");
        return (true);
    }

    private void err(String s) {
        if (standardErr) {
            System.err.print(s);
        }
    }

    private boolean virtualizeThisMethod(InvokeInstruction i) {
        // assume that this method call is correct.. because we'll check that before we call this check
        // first... lets see if we know about it
        String m = i.getMethodName(cpg);
        MethodStruct ms = (MethodStruct) methodByName.get(m);
        if (ms == null) { // unfortunatly... we don't have the bytecode for this method... so lets NOT
            // virtualize it
            //errln(m + " is NOT a known bytecode so we will need to reflect");
            return (false);
        }

        return (true);
    }

    /**
     * Jump to a new method within this class.
     *
     * @param mn
     * @throws IOException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    void start(String mn) throws IOException, InvocationTargetException, IllegalAccessException {
        //  Iterator i = methodObject.keySet().iterator();
        //  boolean found = false;
        MethodStruct ms = (MethodStruct) methodByName.get(mn);
        if (ms == null) {
            throw new Error("Could not find " + mn + " in " + methodByName);
        }
        pointAtNewMethod(ms, null);
    }

    private Class getType(Type t) {
        if (classByType == null) {
            setupClassByTypeMap();
        }

        Class rtn = (Class) classByType.get(t.getSignature());

        if (rtn == null) {
            throw new UnsupportedOperationException("Don't know about " + t);
        }
        return (rtn);
    }

    private void setupClassByTypeMap() {
        classByType = new HashMap<String, Class<? extends Number>>();
        classByType.put("I", Integer.TYPE);
        classByType.put("L", Long.TYPE);
        classByType.put("S", Short.TYPE);
    }

    private void outln(String s) {
        if (standardOut) {
            s = s.trim();
            if (s.length() > 1)
                System.out.println(s);
        }
        // else {
//            s = s.trim();
//            if (s.length() > 1)
//                debugStatements.add(s);
//        }
    }

//    public void setNextEvent (BEvent e) {
//        this.nextEvent = e;
//    }

    public VMState tick() throws InternalVMException {
        try {
            tick++;
            Interupt i = this.intCheck();
            if (!i.equals(Interupt.NONE)) {
                // TODO: repoint at a handler for the interupt...

            }
            boolean finished = nextInstruction();
            if (finished) {
                return (state);
            }

            return (state);
        } catch (IllegalAccessException e) {
            throw new InternalVMException(e);
        } catch (InvocationTargetException e) {
            throw new InternalVMException(e);
        } catch (ClassNotFoundException e) {
            throw new InternalVMException(e);
        } catch (NoSuchFieldException e) {
            throw new InternalVMException(e);
        } catch (RestartException e) {
            throw new InternalVMException(e);
        }
    }

    static ArrayList<Integer> integerCache = null;

    static public Integer getInteger(int x) {

        if (integerCache == null) {
            integerCache = new ArrayList<Integer>();
            for (int a = 0; a < 20000; a++) {
                integerCache.add(a);
            }
        }
        if (x > integerCache.size() - 1) {
            return (x);
        } else {
            return (integerCache.get(x));
        }

    }
// -------------------------- INNER CLASSES --------------------------

    static public void clearInstructionCache() {
        for (TreeMap<Integer, Instruction> tm : cachedInstructions.values()) {
            tm.clear();
        }
        cachedInstructions.clear();
    }

    volatile boolean cacheInstructions = true;

    public void setCacheInstructions(boolean cache) {
        cacheInstructions = cache;

    }

    static HashMap<String, TreeMap<Integer, Instruction>> cachedInstructions = new HashMap<String, TreeMap<Integer, Instruction>>();

    public class UnIntializedObject {
        String className = null;

        public UnIntializedObject(String className) {
            this.className = className;
        }
    }

    private class ReturnMarker {
        MethodStruct method = null;
        private Iterator<Integer> oldIterator;
        ArrayList<Number> vars = null;

        public ReturnMarker(MethodStruct ms, Iterator<Integer> oldIterator) {
            this.method = ms;
            this.oldIterator = oldIterator;
            this.vars = ms.localVariables;
        }

        public MethodStruct getMethodStruct() {
            return (method);
        }

        public ArrayList<Number> getLocalVariables() {
            return (vars);
        }

        public Iterator<Integer> getOldIterator() {
            return (oldIterator);
        }

        public String toString() {
            return ("ReturnMarker to " + method.name);
        }
    }

// --------------------------- main() method ---------------------------

//    public int getId () {
//        if (robotInstance != null) {
//            return (robotInstance.getId ());
//        }
//        return (-1);
//    }

    /**
     * Push the current frame onto the stack and call the method in methodstruct
     * if ReturnMarker rm is null then the frame --> stack is done automatically
     * if it is NOT null.. then we are returning from a previous method call and "popping" back.
     *
     * @param ms
     * @param rm
     */
    void pointAtNewMethod(MethodStruct ms, ReturnMarker rm) { // if this is the entry point... do save off the old stack
        if (rm == null) {  // no return record so we going forward (not returning)
            ReturnMarker rmm = new ReturnMarker(state.currentMethod, currentInstructionIterator);
            state.setPC(0); // start at beginning of the new method
            int count = ms.argCount;

            for (int x = 0; x < count; x++) {
                Number n = (Number) pop();
                ms.localVariables.set(x, n);
            }
            // we also need to put the arguments into our local variables of the new method
            // push(selfInstance);

            // so this little *KLUDGE* is for the the first time we start
            // i.encoder. call public static void main
//            if (!state.stack.isEmpty()) {
//                //  Grab off the reference of the object that we need to call the method on
//                Object instance = pop();
//                System.err.println("weird poppish " + instance);
//            }

            // store off a self instance.. but odd...hmm
            //       methodLocalVariableValues.put("this", selfInstance);
            outln("Pushhing " + rmm);
            push(rmm);
        }

        state.currentMethod = ms;

        if (state.currentMethod ==state.endmarker) {
            errln("This program should end");
            state.markAsDone();
            return;
        }

        if (rm != null) { // if we are indeed returning
            outln("Returning to " + rm.getMethodStruct().name);
            outln("Switching to this old Iterator " + rm.getOldIterator());
            currentInstructionIterator = rm.getOldIterator();
            ms = rm.getMethodStruct();
        } else {  // otherwise just put us at the beginning
            outln("Calling " + ms.name);
            currentInstructionIterator = state.currentMethod.instructions.keySet().iterator();
        }
    }
}


